#include "qjshighlight.h"

QJSHighLight::QJSHighLight(QTextDocument *document) :
    QSyntaxHighlighter(document),
    m_inMultilineComment(false),
    m_braceDepth(0)
{
    m_currentBlockParentheses.reserve(20);
    m_format[C_NUMBER].setForeground(Qt::darkBlue);
    m_format[C_STRING].setForeground(Qt::darkGreen);
    m_format[C_TYPE].setForeground(Qt::darkMagenta);
    m_format[C_KEYWORD].setForeground(Qt::darkYellow);
    m_format[C_FIELD].setForeground(Qt::darkRed);
    m_format[C_COMMENT].setForeground(Qt::darkGreen);
    m_format[C_VISUAL_WHITESPACE].setForeground(Qt::lightGray);
    m_format[C_JS_SCOPE_VAR].setForeground(QColor(41, 133, 199));
    m_format[C_JS_SCOPE_VAR].setFontItalic(true);
}

void QJSHighLight::highlightBlock(const QString &text)
{
    const QList<Token> tokens = m_scanner(text, onBlockStart());

    int index = 0;
    while (index < tokens.size())
    {
        const Token &token = tokens.at(index);

        switch (token.kind)
        {
        case Token::Keyword:
            setFormat(token.offset, token.length, m_format[C_KEYWORD]);
            break;
        case Token::String:
            setFormat(token.offset, token.length, m_format[C_STRING]);
            break;
        case Token::Comment:
            if (m_inMultilineComment && text.midRef(token.end() - 2, 2) == QLatin1String("*/"))
            {
                onClosingParenthesis(QLatin1Char('-'), token.end() - 1, index == tokens.size()-1);
                m_inMultilineComment = false;
            } else if (!m_inMultilineComment
                       && (m_scanner.state() & Scanner::MultiLineMask) == Scanner::MultiLineComment
                       && index == tokens.size() - 1)
            {
                onOpeningParenthesis(QLatin1Char('+'), token.offset, index == 0);
                m_inMultilineComment = true;
            }
            setFormat(token.offset, token.length, m_format[C_COMMENT]);
            break;
        case Token::RegExp:
            setFormat(token.offset, token.length, m_format[C_STRING]);
            break;
        case Token::LeftParenthesis:
            onOpeningParenthesis(QLatin1Char('('), token.offset, index == 0);
            break;
        case Token::RightParenthesis:
            onClosingParenthesis(QLatin1Char(')'), token.offset, index == tokens.size()-1);
            break;
        case Token::LeftBrace:
            onOpeningParenthesis(QLatin1Char('{'), token.offset, index == 0);
            break;
        case Token::RightBrace:
            onClosingParenthesis(QLatin1Char('}'), token.offset, index == tokens.size()-1);
            break;
        case Token::LeftBracket:
            onOpeningParenthesis(QLatin1Char('['), token.offset, index == 0);
            break;
        case Token::RightBracket:
            onClosingParenthesis(QLatin1Char(']'), token.offset, index == tokens.size()-1);
            break;
        case Token::Identifier:
            break;
        case Token::Delimiter:
            break;
        default:
            break;
        }
        ++index;
    }

    int previousTokenEnd = 0;
    for (int index = 0; index < tokens.size(); ++index)
    {
        const Token &token = tokens.at(index);
        setFormat(previousTokenEnd, token.begin() - previousTokenEnd, m_format[C_VISUAL_WHITESPACE]);
        switch (token.kind)
        {
        case Token::Comment:
        case Token::String:
        case Token::RegExp:
        {
            int i = token.begin(), e = token.end();
            while (i < e)
            {
                const QChar ch = text.at(i);
                if (ch.isSpace())
                {
                    const int start = i;
                    do
                    {
                        ++i;
                    } while (i < e && text.at(i).isSpace());
                    setFormat(start, i - start, m_format[C_VISUAL_WHITESPACE]);
                } else {
                    ++i;
                }
            }
        } break;

        default:
            break;
        }

        previousTokenEnd = token.end();
    }

    setFormat(previousTokenEnd, text.length() - previousTokenEnd, m_format[C_VISUAL_WHITESPACE]);
    setCurrentBlockState(m_scanner.state());
    onBlockEnd(m_scanner.state());
}

int QJSHighLight::onBlockStart()
{
    m_currentBlockParentheses.clear();
    m_braceDepth = 0;
    m_inMultilineComment = false;

    int state = 0;
    int previousState = previousBlockState();
    if (previousState != -1)
    {
        state = previousState & 0xff;
        m_braceDepth = (previousState >> 8);
        m_inMultilineComment = ((state & Scanner::MultiLineMask) == Scanner::MultiLineComment);
    }

    return state;
}

void QJSHighLight::onBlockEnd(int state)
{
    setCurrentBlockState((m_braceDepth << 8) | state);
}

void QJSHighLight::onOpeningParenthesis(QChar parenthesis, int pos, bool atStart)
{
    if (parenthesis == QLatin1Char('{') || parenthesis == QLatin1Char('[') || parenthesis == QLatin1Char('+'))
    {
        ++m_braceDepth;
    }
    m_currentBlockParentheses.push_back(Parenthesis(Parenthesis::Opened, parenthesis, pos));
}

void QJSHighLight::onClosingParenthesis(QChar parenthesis, int pos, bool atEnd)
{
    if (parenthesis == QLatin1Char('}') || parenthesis == QLatin1Char(']') || parenthesis == QLatin1Char('-'))
    {
        --m_braceDepth;
    }
    m_currentBlockParentheses.push_back(Parenthesis(Parenthesis::Closed, parenthesis, pos));
}
